/*
 *  ArgumentParsing.cpp
 *
 *  Created by Pete Willemsen on 10/6/09.
 *  Copyright 2009 Department of Computer Science, University of Minnesota-Duluth. All rights reserved.
 *
 * This file is part of CS5721 Computer Graphics library (cs5721Graphics).
 *
 * cs5721Graphics is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * cs5721Graphics is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with cs5721Graphics.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <cassert>
#include "ArgumentParsing.h"

using namespace cs5721;

ArgumentParsing::ArgumentParsing()
{
};

ArgumentParsing::ArgumentParsing(int argc, char *argv[])
{
  process(argc, argv);
};

ArgumentParsing::~ArgumentParsing()
{
}

void ArgumentParsing::reg(const std::string& argName, char shortArgName, int has_argument, bool required)
{
  ModifiedOption nextArg;

  // the option structure uses C-style character strings so get our
  // "string" into that form.
  nextArg.optParams.name = (const char *)malloc(argName.length() + 1);
  strcpy((char *)nextArg.optParams.name, argName.c_str());

  nextArg.optParams.has_arg = has_argument;

  // setting flag to null and setting the short arg char to val will
  // cause getopt_long to act pretty much identical to getopt.
  nextArg.optParams.flag = 0;
  nextArg.optParams.val = shortArgName;

  nextArg.isSet = false;
  nextArg.optionalArgument = "";

  m_ArgVector.push_back(nextArg);
}

bool ArgumentParsing::isSet(const std::string& argName)
{
  for (unsigned int i=0; i<m_ArgVector.size(); ++i)
    if (argName == m_ArgVector[i].optParams.name)
      return m_ArgVector[i].isSet;
  return false;
}

bool ArgumentParsing::isSet(const std::string& argName, std::string &argValue)
{
  for (unsigned int i=0; i<m_ArgVector.size(); ++i)
    if ((argName == m_ArgVector[i].optParams.name) && (m_ArgVector[i].isSet))
      {
	argValue = m_ArgVector[i].optionalArgument;
	return true;
      }

  argValue = "";
  return false;
}

int ArgumentParsing::process(int argc, char *argv[])
{
  // convert the vector into a temporary option struct needed for getopt_long
  option *getoptOptions = new struct option[m_ArgVector.size()];

  for (unsigned int i=0; i<m_ArgVector.size(); i++)
    {
      getoptOptions[i].name = (char *)malloc(strlen(m_ArgVector[i].optParams.name) + 1);
      strcpy((char *)getoptOptions[i].name, m_ArgVector[i].optParams.name);
      getoptOptions[i].has_arg = m_ArgVector[i].optParams.has_arg;
      getoptOptions[i].flag = m_ArgVector[i].optParams.flag;
      getoptOptions[i].val = m_ArgVector[i].optParams.val;

      // std::cout << "longopts[" << i << "]: " 
      // << getoptOptions[i].name << ", " 
      // << getoptOptions[i].has_arg << ", "
      // << getoptOptions[i].flag << ", "
      // << getoptOptions[i].val << std::endl;
    }

  // generate the option string to allow the short name (single char)
  // options to work
  std::string optstring = "";
  for (unsigned int i=0; i<m_ArgVector.size(); ++i)
    {
      optstring += m_ArgVector[i].optParams.val;
      if (m_ArgVector[i].optParams.has_arg == required_argument)
	optstring += ":";
      else if (m_ArgVector[i].optParams.has_arg == optional_argument)
	optstring += "::";
    }
  // std::cout << "optstring = " <<  optstring << std::endl;

  int c = -1;
  while (1)
    {
      int longoption_index = 0;

      c = getopt_long(argc, argv, optstring.c_str(), getoptOptions, &longoption_index);

      // when the getopt functions return a -1, there are no more
      // arguments to process.
      if (c == -1) break;

      // c seems to be correct... BUT longoption_index is not being set correctly...
      // so, override with quick lookup for now... until I can fix this.
      // assert( m_ArgVector[longoption_index].optParams.val == c );
      longoption_index = lookupIndex(c);
      if (longoption_index == -1) break;
	      
      // std::cout << "option_index=" << longoption_index << ", c = " << (char)c 
      // << ", val = " << m_ArgVector[longoption_index].optParams.val 
      // << ", (" << (char)m_ArgVector[longoption_index].optParams.val << ")" << std::endl;
      
      bool found = false;
      if (c == m_ArgVector[longoption_index].optParams.val)
	{
	  m_ArgVector[longoption_index].isSet = true;
	  if (m_ArgVector[longoption_index].optParams.has_arg != no_argument)
	    {
	      m_ArgVector[longoption_index].optionalArgument = optarg;	      
	    }
	  
	  found = true;
	}
      
      if (!found)
	{
	  std::cerr << "?? getopt returned character code: " << c << std::endl;
	}
    }

#if 0
  // Not doing this...
  // look over the non-option elements
  if (optind < argc) {
    printf("non-option ARGV-elements: ");
    while (optind < argc)
      printf("%s ", argv[optind++]);
    printf("\n");
  }
#endif

  // deallocate the memory we created to make this happen
  for (unsigned int i=0; i<m_ArgVector.size(); ++i)
  {
      free((char *)(getoptOptions[i].name));
  }
  
  delete [] getoptOptions;

  return 1;
}

int ArgumentParsing::lookupIndex(char c)
{
  for (unsigned int i=0; i<m_ArgVector.size(); i++)
    {
      if (c == m_ArgVector[i].optParams.val)
	return i;
    }
  return -1;
}
